package org.pwwong.fxscalautils.components


import org.pwwong.scalautils.event.{Event}
import org.pwwong.scalautils.signals.{ConstantSignal,BindableSignal,Signal}
import org.pwwong.scalautils.signals.Predef._
import scala.collection.mutable


object StateMachine {
  
  abstract class State {
    private val enterEventMap = mutable.Map[Symbol, Event[(State,State)]]()
    enterEventMap += ('Any -> Event[(State,State)])
    
    private val exitEventMap = mutable.Map[Symbol, Event[(State,State)]]()
    exitEventMap += ('Any -> Event[(State,State)])
    
    def onEnter:Event[(State,State)] = enterEventMap('Any)
    def onEnterFrom(fromStateSym:Symbol):Event[(State,State)] = {
      if(!enterEventMap.contains(fromStateSym)) enterEventMap+=(fromStateSym -> Event[(State,State)])
      enterEventMap(fromStateSym)
    }
    
    def onExit:Event[(State,State)] = exitEventMap('Any)
    def onExitTo(toStateSym:Symbol):Event[(State,State)] = {
      if(!exitEventMap.contains(toStateSym)) exitEventMap+=(toStateSym -> Event[(State,State)])
      exitEventMap(toStateSym)
    }
    
    val stateMachine:StateMachine
    val isDefault:Boolean
    
    def symbolID:Symbol 
    
    final def goto[T](nextStateFunc:T=>Option[State],onEvent:Event[T]):State = {
      onEvent(State.this){ nextStateFunc(_).foreach( nextState => stateMachine.gotoState(nextState) ) }
      State.this
    }
    final def goto(nextState:State, onEvents:Event[_]*):State = {
      require( stateMachine contains nextState, symbolID+" can't goto "+nextState.symbolID
               +", because "+nextState.symbolID+" doesn't exist in StateMachine.")
      for(onEvent <- onEvents)
        onEvent(State.this){tmp => stateMachine.gotoState(nextState)}
      State.this
    }
    
    override def hashCode = symbolID.hashCode 
  }
  
  protected abstract class DefaultState extends State {
    protected[StateMachine] var _symbolID:Symbol = 'default
    override def symbolID:Symbol = _symbolID
  }
}


class StateMachine {
  import StateMachine._
  
  def on(fromState:Symbol, toState:Symbol):Event[(State,State)] = {
    if(fromState == 'Any){
      if(toState == 'Any) onStateChange
      else getState(toState).onExit
    }else{
      require(contains(fromState))
      getState(fromState).onExitTo(toState)
    }
  }
  def onEnter(toState:Symbol):Event[(State,State)]  = getState(toState).onEnter
  def onExit(fromState:Symbol):Event[(State,State)] = getState(fromState).onExit
  
  private val _stateMap = mutable.Map[Symbol, State]()
  private var _isDefaultSet:Boolean = false
  private var _defaultState:DefaultState = new DefaultState {
                                             override val stateMachine = StateMachine.this
                                             override val isDefault = true
                                           }
  
  final protected val _curState = BindableSignal[State](new ConstantSignal(null)) 
  final val curState:Signal[State] = _curState
  final val onStateChange = Event[(State,State)]
  
  final def addState(newStateSymbolID:Symbol):State = {
    require(!_stateMap.contains(newStateSymbolID), "StateMachine.addState("+newStateSymbolID+") failed: "+newStateSymbolID+" already exists")
    require(isDefaultSet, "StateMachine.addState("+newStateSymbolID+") failed: default state needs to be set before adding other states")
    
    val newState = new State{
      override val stateMachine = StateMachine.this
      override val isDefault = true
      override def symbolID = newStateSymbolID
    }
    _stateMap+=(newStateSymbolID -> newState)
    newState
  }
  
  final def gotoState(nextStateSymbolID:Symbol){
    require(_stateMap.contains(nextStateSymbolID)) //, "Statemachine.gotoState("+nextStateSymbolID+") failed: "+nextStateSymbolID+" isn't part of this StateMachine")
    val nextState = getState(nextStateSymbolID)
    nextState.onEnter.fire(_curState() -> nextState)
    _curState <= nextState
  }
  final def gotoState(nextState:State){
    require(nextState.stateMachine == this) //, "Statemachine.gotoState("+nextState.symbolID+") failed: "+nextState.symbolID+" isn't part of this StateMachine")
    nextState.onEnter.fire(_curState() -> nextState)
    _curState <= nextState
  }
  
  final def isDefaultSet = _isDefaultSet
  final val defaultState:State = _defaultState
  final def setDefaultState( newDefaultStateSymbolID:Symbol ):State = {
    require(!_isDefaultSet, "StateMachine.defaultState_ failed: defaultState already set")
    
    _isDefaultSet = true
    _defaultState._symbolID = newDefaultStateSymbolID
    _stateMap+=(_defaultState._symbolID -> _defaultState)
    _defaultState
  }
  
  final def getState(stateSymbol:Symbol):State = {
    require(contains(stateSymbol), "StateMachine.getState("+stateSymbol+") failed: "+stateSymbol+" state doesn't exist")
    
    _stateMap(stateSymbol)
  }
  
  final def contains(s:State) = _stateMap.values.contains(s)
  final def contains(sym:Symbol) = _stateMap.contains(sym)
}
